package com.nowcoder.topic.pointers.hard;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;

/**
 * NC343 和大于等于K的最短子数组
 * @author d3y1
 */
public class NC343{
    /**
     * 代码中的类名、方法名、参数名已经指定，请勿修改，直接返回方法规定的值即可
     *
     *
     * @param nums int整型ArrayList
     * @param k int整型
     * @return int整型
     */
    public int shortestSubarray (ArrayList<Integer> nums, int k) {
        // return solution1(nums, k);
        // return solution11(nums, k);
        return solution2(nums, k);
        // return solution3(nums, k);
    }

    /**
     * 二分+前缀和
     *
     * num值不可为负数, 以保证使用二分法的前提(单调性)
     *
     * @param nums 正整数数组!
     * @param k
     * @return
     */
    private int solution1(ArrayList<Integer> nums, int k){
        int n = nums.size();

        // 前缀和
        long[] preSum = new long[n+1];
        for(int i=1; i<=n; i++){
            preSum[i] = preSum[i-1]+nums.get(i-1);
        }

        int result = Integer.MAX_VALUE;
        // 窗口大小
        int left = 1;
        int right = n;
        int mid;
        // 二分
        while(left <= right){
            mid = left+(right-left)/2;
            if(check(preSum, k, mid)){
                result = Math.min(result, mid);
                right = mid-1;
            }else{
                left = mid+1;
            }
        }

        return result==Integer.MAX_VALUE?-1:result;
    }

    /**
     * 校验gap大小窗口是否满足条件(preSum[j]-preSum[i] >= k)
     * @param preSum
     * @param target
     * @param gap
     * @return
     */
    private boolean check(long[] preSum, int target, int gap){
        for(int i=0,j=i+gap; j<preSum.length; i++,j++){
            if(preSum[j]-preSum[i] >= target){
                return true;
            }
        }

        return false;
    }

    /**
     * 二分+前缀和
     *
     * num值不可为负数, 以保证使用二分法的前提(单调性)
     *
     * @param nums 正整数数组!
     * @param k
     * @return
     */
    private int solution11(ArrayList<Integer> nums, int k){
        int n = nums.size();

        // 前缀和
        long[] preSum = new long[n+1];
        for(int i=1; i<=n; i++){
            preSum[i] = preSum[i-1]+nums.get(i-1);
        }

        int result = Integer.MAX_VALUE;
        // 前缀数组索引
        int left,right,mid;
        for(int i=0; i<=n; i++){
            left = i;
            right = n;
            // 二分
            while(left <= right){
                mid = left+(right-left)/2;
                // left最终指向第一个满足条件(preSum[left]-preSum[i]>=k)的位置(从左往右)
                if(preSum[mid]-preSum[i] < k){
                    left = mid+1;
                }else{
                    right = mid-1;
                }
            }
            // 当前未找到满足条件位置 后续更不可能满足条件(直接结束循环)
            if(left > n){
                break;
            }
            result = Math.min(result, left-i);
        }

        return result==Integer.MAX_VALUE?-1:result;
    }

    /**
     * 双指针(毛毛虫)
     *
     * num值可为负数
     *
     * 相似 -> NC41 最长无重复子数组            [nowcoder]
     * 相似 -> NC170 最长不含重复字符的子字符串   [nowcoder]
     * 相似 -> NC356 至多包含K种字符的子串       [nowcoder]
     * 相似 -> NC387 找到字符串中的异位词        [nowcoder]
     *
     * @param nums 正整数数组!
     * @param k
     * @return
     */
    private int solution2(ArrayList<Integer> nums, int k){
        int n = nums.size();
        int result = Integer.MAX_VALUE;

        // 双指针(毛毛虫)
        int left=0;
        int right=0;
        int sum = 0;
        int numL,numR;
        while(right < n){
            numR = nums.get(right++);
            if(numR >= k){
                return 1;
            }

            sum += numR;

            while(left<=right && sum>=k){
                result = Math.min(result, right-left);
                numL = nums.get(left++);
                sum -= numL;
            }
        }

        return result==Integer.MAX_VALUE?-1:result;
    }

    /**
     * 单调栈(双端队列Deque)[单调增]+前缀和
     *
     * num值可为负数
     *
     * @param nums 整数数组!
     * @param k
     * @return
     */
    private int solution3(ArrayList<Integer> nums, int k){
        int n = nums.size();

        // 前缀和
        long[] preSum = new long[n+1];
        for(int i=1; i<=n; i++){
            preSum[i] = preSum[i-1]+nums.get(i-1);
        }

        int result = Integer.MAX_VALUE;

        // 双端队列
        Deque<Integer> queue = new ArrayDeque<>();
        for(int i=0; i<=n; i++){
            long curSum = preSum[i];
            // 判断 是否满足条件(>=k)
            while(!queue.isEmpty() && curSum-preSum[queue.peekFirst()]>=k){
                result = Math.min(result, i-queue.pollFirst());
            }
            // 单调栈(单调增)
            while(!queue.isEmpty() && preSum[queue.peekLast()]>=curSum){
                queue.pollLast();
            }
            queue.offerLast(i);
        }

        return result==Integer.MAX_VALUE?-1:result;
    }
}